package com.gitee.magic.framework.base.rest;

import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.util.MimeType;

import com.gitee.magic.context.ConverterEditorUtils;
import com.gitee.magic.core.exception.ApplicationException;
import com.gitee.magic.core.json.JsonObject;
import com.gitee.magic.core.utils.GzipComperssedUtils;
import com.gitee.magic.core.utils.codec.Base64;
import com.gitee.magic.core.utils.reflect.TypeReference;
import com.gitee.magic.framework.base.constant.Config;
import com.gitee.magic.framework.base.context.Http;
import com.gitee.magic.framework.head.utils.CodecUtils;
import com.gitee.magic.framework.head.vo.BaseVO;
import com.gitee.magic.framework.head.vo.ListVO;
import com.gitee.magic.framework.head.vo.ObjectVO;
import com.gitee.magic.framework.head.vo.PageVO;
import com.gitee.magic.framework.head.vo.QueryVO;

/**
 * @author start
 */
public class ApiRestTemplate {

	private String host;
	private String accessId;
	private String accessKey;
	private String contentType;
	private Map<String,String> headers=new HashMap<>();
	
	public ApiRestTemplate() {
		this(null);
	}
	
	public ApiRestTemplate(String host) {
		this(host,null,null);
	}
	
	public ApiRestTemplate(String accessId,String accessKey) {
		this(null,accessId,accessKey);
	}
	
	public ApiRestTemplate(String host,String accessId,String accessKey) {
		this(host,accessId,accessKey,HttpWrapper.CONTENTTYPE_JSON);
	}
	
	public ApiRestTemplate(String host,String accessId,String accessKey,String contentType) {
		this.host=host;
		this.accessId=accessId;
		this.accessKey=accessKey;
		this.contentType=contentType;
	}
	
	/**
	 * 设置授权码
	 * @param authorization
	 */
	public void setAuthorization(String authorization) {
		setHeader("Authorization", authorization);
	}
	
	///////////////GET
	
	public void get(String uri){
		get(uri, null);
	}
	
	public <T>void get(String uri,T requestPrototype){
		forBase(uri,RequestMethodEnum.GET,requestPrototype);
	}
	
	public <R>R getObject(String uri,Class<R> responsePrototype){
		return getObject(uri, null,responsePrototype);
	}
	
	public <T,R>R getObject(String uri,T requestPrototype,Class<R> responsePrototype){
		return getObject(uri,requestPrototype, new TypeReference<ObjectVO<R>>(responsePrototype) {});
	}
	
	public <T,R>R getObject(String uri,T requestPrototype,TypeReference<ObjectVO<R>> typeReference){
		return forObject(uri, RequestMethodEnum.GET, requestPrototype, typeReference);
	}
	
	public <R>List<R> getList(String uri,Class<R> responsePrototype){
		return getList(uri,null,responsePrototype);
	}
	
	public <T,R>List<R> getList(String uri,T requestPrototype,Class<R> responsePrototype){
		return getList(uri, requestPrototype, new TypeReference<ListVO<R>>(responsePrototype) {});
	}
	
	public <T,R>List<R> getList(String uri,T requestPrototype,TypeReference<ListVO<R>> typeReference){
		return forList(uri, RequestMethodEnum.GET, requestPrototype, typeReference);
	}
	
	public <R>QueryVO<R> getQuery(String uri,Class<R> responsePrototype){
		return getQuery(uri, null,responsePrototype);
	}
	
	public <T,R>QueryVO<R> getQuery(String uri,T requestPrototype,Class<R> responsePrototype){
		return getQuery(uri, requestPrototype, new TypeReference<PageVO<R>>(responsePrototype) {});
	}
	
	public <T,R>QueryVO<R> getQuery(String uri,T requestPrototype,TypeReference<PageVO<R>> typeReference){
		return forQuery(uri, RequestMethodEnum.GET, requestPrototype, typeReference);
	}
	
	///////////////POST
	
	public void post(String uri){
		post(uri, null);
	}
	
	public <T>void post(String uri,T requestPrototype){
		forBase(uri,RequestMethodEnum.POST,requestPrototype);
	}
	
	public <R>R postObject(String uri,Class<R> responsePrototype){
		return postObject(uri, null,responsePrototype);
	}
	
	public <T,R>R postObject(String uri,T requestPrototype,Class<R> responsePrototype){
		return postObject(uri,requestPrototype, new TypeReference<ObjectVO<R>>(responsePrototype) {});
	}
	
	public <T,R>R postObject(String uri,T requestPrototype,TypeReference<ObjectVO<R>> typeReference){
		return forObject(uri, RequestMethodEnum.POST, requestPrototype, typeReference);
	}
	
	public <R>List<R> postList(String uri,Class<R> responsePrototype){
		return postList(uri,null,responsePrototype);
	}
	
	public <T,R>List<R> postList(String uri,T requestPrototype,Class<R> responsePrototype){
		return postList(uri, requestPrototype, new TypeReference<ListVO<R>>(responsePrototype) {});
	}
	
	public <T,R>List<R> postList(String uri,T requestPrototype,TypeReference<ListVO<R>> typeReference){
		return forList(uri, RequestMethodEnum.POST, requestPrototype, typeReference);
	}
	
	public <R>QueryVO<R> postQuery(String uri,Class<R> responsePrototype){
		return postQuery(uri, null,responsePrototype);
	}
	
	public <T,R>QueryVO<R> postQuery(String uri,T requestPrototype,Class<R> responsePrototype){
		return postQuery(uri, requestPrototype, new TypeReference<PageVO<R>>(responsePrototype) {});
	}
	
	public <T,R>QueryVO<R> postQuery(String uri,T requestPrototype,TypeReference<PageVO<R>> typeReference){
		return forQuery(uri, RequestMethodEnum.POST, requestPrototype, typeReference);
	}
	
	///////////////PUT
	
	public void put(String uri){
		put(uri, null);
	}
	
	public <T>void put(String uri,T requestPrototype){
		forBase(uri,RequestMethodEnum.PUT,requestPrototype);
	}

	///////////////DELETE
	
	public void delete(String uri){
		delete(uri, null);
	}
	
	public <T>void delete(String uri,T requestPrototype){
		forBase(uri,RequestMethodEnum.DELETE,requestPrototype);
	}
	
	////////////////
	
	public <T>void forBase(String uri,RequestMethodEnum method,T requestPrototype) {
		String responseContent=requestForString(uri,method,requestPrototype);
		ResponseResolve resolve=new ResponseResolve(responseContent);
		BaseVO response=resolve.analysis(BaseVO.class);
		resolve.checkResponse(response);
	}
	
 	public <T,R>R forObject(String uri,RequestMethodEnum method,T requestPrototype,TypeReference<ObjectVO<R>> typeReference){
 		String responseContent=requestForString(uri,method,requestPrototype);
		ResponseResolve resolve=new ResponseResolve(responseContent);
		ObjectVO<R> response=resolve.analysis(ObjectVO.class,typeReference);
		resolve.checkResponse(response);
		return response.getData();
	}
	
	public <T,R>List<R> forList(String uri,RequestMethodEnum method,T requestPrototype,TypeReference<ListVO<R>> typeReference){
		String responseContent=requestForString(uri,method,requestPrototype);
		ResponseResolve resolve=new ResponseResolve(responseContent);
		ListVO<R> response=resolve.analysis(ListVO.class,typeReference);
		resolve.checkResponse(response);
		return response.getData();
	}
	
	public <T,R>QueryVO<R> forQuery(String uri,RequestMethodEnum method,T requestPrototype,TypeReference<PageVO<R>> typeReference){
		String responseContent=requestForString(uri,method,requestPrototype);
		ResponseResolve resolve=new ResponseResolve(responseContent);
		PageVO<R> response=resolve.analysis(PageVO.class,typeReference);
		resolve.checkResponse(response);
		return new QueryVO<>(response.getData(),response.getTotal());
	}

	public <T>String requestForString(String uri,RequestMethodEnum method,T requestPrototype) {
		if(method==RequestMethodEnum.GET) {
			return requestUrlForEntity(uri,requestPrototype,RequestMethodEnum.GET);
		}else {
			return requestBodyForEntity(uri,requestPrototype,method);
		}
	}
	
	///////////////GET
	
	public <T>String requestUrlForEntity(String uri,T requestPrototype,RequestMethodEnum method){
		if(requestPrototype!=null) {
			Map<String,Object> params=ConverterEditorUtils.converter(requestPrototype, JsonObject.class).toMap();
			return requestUrlForMap(uri,params,method);
		}else {
			return requestUrlForMap(uri,new HashMap<>(0),method);
		}
	}
	
	public String requestUrlForMap(String uri,Map<String,Object> params,RequestMethodEnum method){
		Map<String,String> trees=new TreeMap<>();
		if(getAccessId()!=null) {
			trees.put(Http.ACCESSID, getAccessId());
		}
		for(String key:params.keySet()) {
			trees.put(key, String.valueOf(params.get(key)));
		}
		if(getAccessKey()!=null) {
			String content=HttpClient.bodyUrlEncodeString(trees);
			trees.put(Http.SIGNATURE, CodecUtils.signatureHmacSha1(getAccessKey(), content));
		}
		HttpRequest request=new HttpRequest(getHost()+uri);
		request.setRequestMethod(method);
		request.setUrlParams(trees);
		request.getHeaders().putAll(headers);
		return requestConnection(request);
	}

	///////////////POST、PUT、DELETE
	
	public <T>String requestBodyForEntity(String uri,T requestPrototype,RequestMethodEnum method){
		String requestContent="{}";
		if(requestPrototype!=null) {
			requestContent=ConverterEditorUtils.converter(requestPrototype, Object.class).toString();
		}
		return requestBodyForString(uri,requestContent,method);
	}
	
	public String requestBodyForMap(String uri,Map<String,Object> bodyParams,RequestMethodEnum method){
		String requestContent=new JsonObject(bodyParams).toString();
		return requestBodyForString(uri,requestContent,method);
	}
	
	public String requestBodyForString(String uri,String requestContent,RequestMethodEnum method){
		return requestBodyForString(uri, requestContent,contentType,method);
	}
	
	public String requestBodyForString(String uri,String requestContent,String contentType,RequestMethodEnum method){
		MimeType mType=MimeType.valueOf(contentType);
		if(MimeType.valueOf(HttpWrapper.CONTENTTYPE_JSON).isCompatibleWith(mType)) {
			try {
				requestContent=Base64.encode(requestContent.getBytes(Config.getEncoding()));
			} catch (UnsupportedEncodingException e) {
				throw new ApplicationException(e);
			}
		} else if(MimeType.valueOf(HttpWrapper.CONTENTTYPE_GZIP).isCompatibleWith(mType)) {
			requestContent=GzipComperssedUtils.compressed(requestContent);
		}else {
			//原文
		}
		HttpRequest request=new HttpRequest(getHost()+uri);
		request.setRequestMethod(method);
		request.setHeader(HttpWrapper.CONTENTTYPE, contentType);
		if(getAccessId()!=null) {
			request.setHeader(Http.ACCESSID, CodecUtils.encode(getAccessId(),Config.getEncoding()));
		}
		if(getAccessKey()!=null) {
			request.setHeader(Http.SIGNATURE, CodecUtils.encode(CodecUtils.signatureHmacSha1(getAccessKey(), requestContent),Config.getEncoding()));
		}
		request.getHeaders().putAll(headers);
		request.setBodyContent(requestContent);
		return requestConnection(request);
	}
	
	private String requestConnection(HttpRequest request) {
		HttpWrapper wrapper=new HttpWrapper();
		HttpURLConnection connection=wrapper.requestConnect(request);
		String responseData=wrapper.start(request, connection);
		MimeType mType=MimeType.valueOf(connection.getContentType());
		String encoding=mType.getCharset()==null?Config.getEncoding():mType.getCharset().name();
		if(MimeType.valueOf(HttpWrapper.CONTENTTYPE_GZIP).isCompatibleWith(mType)) {
			try {
				return new String(GzipComperssedUtils.decompressed(responseData),encoding);
			} catch (UnsupportedEncodingException e) {
				throw new ApplicationException(e);
			}
		}else {
			return responseData;
		}
	}
	
	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public String getAccessId() {
		return accessId;
	}

	public void setAccessId(String accessId) {
		this.accessId = accessId;
	}
	
	public String getAccessKey() {
		return accessKey;
	}

	public void setAccessKey(String accessKey) {
		this.accessKey = accessKey;
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}
	
	public void setHeader(String key,String value) {
		this.headers.put(key, value);
	}
	
}
